﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Collections.Concurrent;
using System.Xml;
using System.Xml.Linq;
using System.Collections;
using System.Security.Cryptography;
using System.Diagnostics;
using uk.ac.soton.ses.DAL;

namespace uk.ac.soton.ses
{
    public sealed class HDCWatcherQueueProcessing
    {
        //Read only and static fields for thread safety. Static contructors are only called once and readonly ensures no threads can re-instantiate the objects.
        //We are using a thread safe collection, even though we only permit one call to ProcessWorkQueue at a time.
        private static readonly BlockingCollection<HDCWatcherQueueItem> _workItems = new BlockingCollection<HDCWatcherQueueItem>(new HDCWatcherQueue());
        private static readonly object _processingLock = new object();
        private static string _currentWorkItemKey;

        public static void ProcessWorkItemsInQueue()
        {
            //We don't want to have more than one call to this at once
            if (!Monitor.TryEnter(_processingLock))
            {
                //Already busy so exit
                return;
            }

            //Now we have the lock, we can take an item and work with it.
            try
            {
                if (_workItems.Count > 0)
                {
                    string message = string.Format("Found {0} items on work queue.", _workItems.Count);
                    HDCFileSystemWatcher.Message(message, TraceLevel.Info, ConsoleColor.Cyan);
                }

                //We keep a list of items that need reprocessing and add after processing the queue.
                //If we add it to the original collection, we will not give enough of a delay and we will be stuck in
                //a loop. For example, someone copying a large file will take a while, but we will constantly be trying
                //access that file in a loop, rather than waiting for the next time (about 5 seconds).
                Queue<HDCWatcherQueueItem> returnedItems = new Queue<HDCWatcherQueueItem>();
                HDCWatcherQueueItem workItem = null;

                //Now iterate around the list
                while (_workItems.TryTake(out workItem))
                {
                    string message = string.Format("Work item started: {0}.", workItem.Key);
                    HDCFileSystemWatcher.Message(message, TraceLevel.Info);
                    _currentWorkItemKey = workItem.Key;

                    if (workItem.IsValid())
                    {
                        try
                        {
                            workItem.DoWork();
                        }
                        catch (Exception e)
                        {
                            message = string.Format("Error: Unhandled exception when processing queue item. Stopping processing this item.");
                            HDCFileSystemWatcher.Message(message, TraceLevel.Error);
                            HDCFileSystemWatcher.Message(e.Message, TraceLevel.Error);
                            HDCFileSystemWatcher.Message(e.StackTrace, TraceLevel.Error);
                        }
                    }

                    if (workItem.QueueItemState == HDCWatcherQueueItemState.RetryRequired)
                    {
                        //Add it back for reprocessing during next timer event
                        workItem.QueueItemState = HDCWatcherQueueItemState.New;
                        returnedItems.Enqueue(workItem);
                    }

                    message = string.Format("Work item complete: {0}. {1} left in queue.", workItem.Key, _workItems.Count);
                    HDCFileSystemWatcher.Message(message, TraceLevel.Info);
                }

                //Put returned items back so they can be reprocessed
                while (returnedItems.Count > 0)
                {
                    HDCWatcherQueueItem returnedItem = returnedItems.Dequeue();
                    AddItemToQueue(returnedItem);
                }
            }
            finally
            {
                //Ensure that the lock is released even if things go bad
                _currentWorkItemKey = "";
                Monitor.Exit(_processingLock);
            }
        }

        private static void AddItemToQueue(HDCWatcherQueueItem newItem)
        {
            try
            {
                if (_currentWorkItemKey != newItem.Key)
                {
                    bool added = _workItems.TryAdd(newItem);
                    HDCFileSystemWatcher.Message(string.Format("{0} added to queue.", newItem.Key), TraceLevel.Info);
                }
                else
                {
                    HDCFileSystemWatcher.Message(string.Format("{0} not added to queue (already present).", newItem.Key), TraceLevel.Info);
                }
            }
            catch (InvalidOperationException)
            {
                //This is thrown if "The underlying collection didn't accept the item"
                //As the queue does not add items that already exist, we will get this exception a lot
                HDCFileSystemWatcher.Message(string.Format("{0} not added to queue (already present).", newItem.Key), TraceLevel.Info);
            }
        }

        public static HDCWatcherQueueItem_MetadataCheck QueueMetadataCheck(HDCFileSystemWatcher fsw)
        {
            HDCWatcherQueueItem_MetadataCheck newWorkItem = new HDCWatcherQueueItem_MetadataCheck(fsw);
            AddItemToQueue(newWorkItem);
            return newWorkItem;
        }

        public static HDCWatcherQueueItem_FolderCheckQuick QueueFolderCheck(string folder, HDCFileSystemWatcher fsw)
        {
            HDCWatcherQueueItem_FolderCheckQuick newWorkItem = new HDCWatcherQueueItem_FolderCheckQuick(folder, fsw);
            AddItemToQueue(newWorkItem);
            return newWorkItem;
        }

        public static HDCWatcherQueueItem_FolderCheckWithEvent QueueFolderCheck(FileSystemEventArgs e, HDCFileSystemWatcher fsw)
        {
            HDCWatcherQueueItem_FolderCheckWithEvent newWorkItem = new HDCWatcherQueueItem_FolderCheckWithEvent(e, fsw);
            AddItemToQueue(newWorkItem);
            return newWorkItem;
        }

        public static void QueueFolderChecksInWatchFolder(HDCFileSystemWatcher fsw)
        {
            DescendTreeAndCheckExperiments(fsw.WatchFolder, fsw.BasePathDepthInWatchFolder, fsw);
        }

        private static void DescendTreeAndCheckExperiments(string path, int numOfLevelsToDescend, HDCFileSystemWatcher fsw)
        {
            foreach (string s in Directory.GetDirectories(path))
            {
                if (numOfLevelsToDescend > 0)
                {
                    HDCFileSystemWatcher.Message(string.Format("Descending into {0}", s), TraceLevel.Info);
                    DescendTreeAndCheckExperiments(s, numOfLevelsToDescend - 1, fsw);
                }
                else
                {
                    QueueFolderCheck(s, fsw);
                }
            }
        }

    }
}
